﻿namespace Hopfield.Simulator
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Data;
    using System.Globalization;
    using System.Text;
    using System.Threading;
    using System.Windows.Forms;

    [HopfieldSimulatorPage("Predictor")]
    public partial class HopfieldPredictor : UserControl, IHopfieldSimulatorPage
    {
        #region Fields

        private static object Mutex = new object();
        private readonly ToolStripStatusLabel info;
        private readonly ToolStripStatusLabel infoMSE;
        private readonly Form parentForm;
        private int pN1 = 6;
        private int pN2 = 10;
        private int pDelta = 0;

        #endregion

        #region Constructor

        public HopfieldPredictor(Hopfield.Math.Hopfield hopfield,
                          ToolStripStatusLabel info,
                          ToolStripStatusLabel infoMSE,
                          Form parentForm)
        {
            InitializeComponent();

            // Dependency injection from parent.
            this.Hopfield = hopfield;
            this.info = info;
            this.infoMSE = infoMSE;
            this.parentForm = parentForm;

            // Set d
            int d = 128;
            this.Hopfield.Setd(d);
            this.textBoxD.Text = d.ToString(CultureInfo.InvariantCulture);

            // Set eta
            this.Hopfield.Seteta(0.7f);
            this.textBoxEta.Text = (0.7f).ToString(CultureInfo.InvariantCulture);

            // Set MSE
            this.Hopfield.SetMSEmin(0.3f);

            // N1, N2
            this.textBoxN1.Text = this.pN1.ToString(CultureInfo.InvariantCulture);
            this.textBoxN2.Text = this.pN2.ToString(CultureInfo.InvariantCulture);

            // Delta
            this.textBoxDelta.Text = this.pDelta.ToString(CultureInfo.InvariantCulture);

            this.dataGridView.AutoGenerateColumns = true;            
        }

        #endregion

        #region Finalizer

        ~HopfieldPredictor()
        {
        }

        #endregion

        #region Properties

        Hopfield.Math.Hopfield hopfield = null;

        public Hopfield.Math.Hopfield Hopfield
        {
            get { return this.hopfield; }
            private set { this.hopfield = value; }
        }

        #endregion

        #region Methods

        public void Activate()
        {
            int d = 0;
            if (true == int.TryParse(textBoxD.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out d))
            {
                this.Hopfield.Setd(d);

                if (this.ChangedD != null)
                {
                    this.ChangedD(this, EventArgs.Empty);
                }
            }

            float eta = 0.7f;
            if (true == float.TryParse(textBoxEta.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out eta))
            {
                this.Hopfield.Seteta(eta);

                if (this.ChangedEta != null)
                {
                    this.ChangedEta(this, EventArgs.Empty);
                }
            }

            int n = 0;
            if (true == int.TryParse(textBoxN1.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out n))
            {
                this.pN1 = n;
            }

            if (true == int.TryParse(textBoxN2.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out n))
            {
                this.pN2 = n;
            }

            int delta = 0;
            if (true == int.TryParse(textBoxDelta.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out delta))
            {
                this.pDelta = delta;
            }

            this.PredictorGridReCreate(this.pN1);
            this.Hopfield.Recreate(this.pN1 * this.pN2);
        }

        private void PredictorGridReCreate(int columnsNumber)
        {
            this.dataTable.BeginInit();

            this.dataTable.Clear();
            this.dataTable.Columns.Clear();

            for (int i = 0; i < columnsNumber; i++)
            {
                DataColumn dc = new DataColumn()
                {
                    AllowDBNull = false,
                    ColumnName = "V" + i,
                    DataType = typeof(int),
                    DefaultValue = 0
                };
                this.dataTable.Columns.Add(dc);
            }

            this.dataTable.EndInit();
        }

        private void OpenHNFile()
        {
            this.Invoke(new MethodInvoker(() =>
            {
                this.info.Text = "Opening...";
                this.dataGridView.DataSource = null;
            }));

            string fileName = string.Empty;

            this.Invoke(new MethodInvoker(() =>
            {
                using (OpenFileDialog ofd = new OpenFileDialog())
                {
                    ofd.Filter = "Hopfiled Predictor File (*.hpf)|*.hpf";
                    if (ofd.ShowDialog() != DialogResult.OK) return;

                    fileName = ofd.FileName;
                }
            }));

            if (true == string.IsNullOrEmpty(fileName)) return;

            DataSet ds = new DataSet("Predictor");
            ds.Locale = new CultureInfo("en-US");
            try
            {
                ds.Tables.Add(this.dataTable);
                this.dataTable.Rows.Clear();
                ds.ReadXml(fileName);
            }
            finally
            {
                ds.Tables.Remove(dataTable);                
            }

            this.Invoke(new MethodInvoker(() =>
            {                
                this.dataGridView.DataSource = this.dataTable;
                this.dataGridView.Refresh();
                this.info.Text = "Ready.";
            }));
        }

        private void ThreadOpenHNFile()
        {
            lock (Mutex)
            {
                this.OpenHNFile();
            }
        }

        public void FileOpen()
        {
            Thread th = new Thread(new ThreadStart(this.ThreadOpenHNFile));
            th.Start();
        }

        private void SaveHNFile()
        {
            this.Invoke(new MethodInvoker(() =>
            {
                this.info.Text = "Saving.";
            }));

            string fileName = string.Empty;

            this.Invoke(new MethodInvoker(() =>
            {
                using (SaveFileDialog sfd = new SaveFileDialog())
                {
                    sfd.Filter = "Hopfiled Predictor File (*.hpf)|*.hpf";
                    if (sfd.ShowDialog() != DialogResult.OK) return;

                    fileName = sfd.FileName;
                }
            }));

            if (true == string.IsNullOrEmpty(fileName)) return;

            DataSet ds = new DataSet("Predictor");
            ds.Locale = new CultureInfo("en-US");

            try
            {
                ds.Tables.Add(this.dataTable);
                ds.WriteXml(fileName);
            }
            finally
            {
                ds.Tables.Remove(this.dataTable);
            }

            this.Invoke(new MethodInvoker(() =>
            {
                this.info.Text = "Ready.";
            }));
        }

        private void ThreadSaveHNFile()
        {
            lock (Mutex)
            {
                this.SaveHNFile();
            }
        }

        public void FileSave()
        {
            Thread th = new Thread(new ThreadStart(this.ThreadSaveHNFile));
            th.Start();
        }

        private void PredictorAttractorsReCreate()
        {
            this.Invoke(new MethodInvoker(() =>
            {
                dataGridView.Enabled = false;
            }));
            try
            {
                if (this.Hopfield.NCount != this.pN1 * this.pN2)
                {
                    this.Hopfield.Recreate(this.pN1 * this.pN2);
                }

                for (int i = this.Hopfield.LCount - 1; i >= 0; i--)
                {
                    this.Hopfield.DelAttractor(i);
                }

                for (int i = 0; i < this.dataGridView.Rows.Count; i++)
                {
                    bool validAttractor = true;
                    float[] y = new float[this.pN1 * this.pN2];
                    for (int j = 0; j < this.dataGridView.Columns.Count; j++)
                    {
                        validAttractor = this.dataGridView.Rows[i].Cells[j].Value != null;
                        if (false == validAttractor)
                            break;

                        int v = (int)this.dataGridView.Rows[i].Cells[j].Value;
                        for (int k = 0; k < this.pN2; k++)
                        {
                            y[j * this.pN2 + k] = this.Hopfield.GetFValue(v);
                        }
                    }

                    if (true == validAttractor)
                    {
                        this.Hopfield.AddPredictorAttractor(y);
                    }
                }
            }
            finally
            {
                this.Invoke(new MethodInvoker(() =>
                {
                    this.dataGridView.Enabled = true;
                }));
            }
        }

        private void LearningToMSEmin(string kind)
        {
            string k = kind.ToString();

            if (this.Hopfield.LCount != this.dataTable.Rows.Count ||
                this.Hopfield.NCount != this.pN1 * this.pN2)
            {
                this.PredictorAttractorsReCreate();
                this.Hopfield.ClearW();
            }

            this.Invoke(new MethodInvoker(() =>
            {
                this.info.Text = "Learning...";
            }));

            float mse = 1.0f;
            float mseL = mse;
            float mseLL = mseL;
            do
            {
                mseLL = mseL;
                mseL = mse;
                mse = this.Hopfield.LearnStepHN(k);
                this.Invoke(new MethodInvoker(() =>
                {
                    this.infoMSE.Text = string.Format(CultureInfo.InvariantCulture, "MSE = {0}", mse.ToString(CultureInfo.InvariantCulture));
                }
                ));
            }
            while (mse < mseL || mseL < mseLL || mse < mseLL);

            this.Invoke(new MethodInvoker(() =>
            {
                this.info.Text = "Ready.";
            }));
        }

        private void ThreadLearnMethod(object kind)
        {
            string k = kind.ToString();
            lock (Mutex)
            {
                this.LearningToMSEmin(k);
            }
        }

        public void SimulatorLearn()
        {
            ParameterizedThreadStart threadStart = new ParameterizedThreadStart(this.ThreadLearnMethod);
            Thread th = new Thread(threadStart);
            // 1st kind of learning - h - one step
            // 2nd kind of learning - d - recursive, the best
            // 3rd kind of learning - z - recursive, worst then above
            // 4th kind of learning - p - recursive, for predictor
            // 5th kind of learning - s - recursive, for sequences
            th.Start("p");
        }

        private void SimulateToMSEmin(object kind)
        {
            string k = kind.ToString();

            if (this.Hopfield.LCount != this.dataTable.Rows.Count ||
                this.Hopfield.NCount != this.pN1 * this.pN2)
                this.LearningToMSEmin("p");

            this.Invoke(new MethodInvoker(() =>
            {
                this.info.Text = "Simulating...";
            }));

            this.Hopfield.LoadKtoY(this.Hopfield.LCount - 1);

            float mse = 1.0f;
            float mseMin = mse;
            float mseMinL = mseMin;
            do
            {
                mseMinL = mseMin;
                mseMin = mse;
                mse = this.Hopfield.CompHN(k);
                // E = h.Energy;
                this.Invoke(new MethodInvoker(() =>
                {
                    this.infoMSE.Text = string.Format(CultureInfo.InvariantCulture, "MSE = {0}", mse.ToString(CultureInfo.InvariantCulture));
                }
                ));
            }
            while (mse < mseMin || mseMin < mseMinL || mse < mseMinL);

            float[] y = this.Hopfield.Y;
            int[] row = new int[this.pN1];

            for (int i = 0; i < this.pN1; i++)
            {
                float r = 0.0f;
                for (int j = 0; j < this.pN2; j++)
                {
                    r += y[i * this.pN2 + j] / ((float)this.pN2);
                }

                row[i] = this.Hopfield.GetValueF(r) + this.pDelta;
            }

            this.Invoke(new MethodInvoker(() =>
            {
                this.dataTable.Rows.Add();
                DataRow dr = this.dataTable.Rows[this.dataTable.Rows.Count - 1];

                for (int i = 0; i < this.pN1; i++)
                {
                    dr[i] = row[i];
                }

                this.info.Text = "Ready.";
            }
            ));
        }

        private void ThreadSimulateMethod(object kind)
        {
            string k = kind.ToString();
            lock (Mutex)
            {
                this.SimulateToMSEmin(k);
            }
        }

        public void SimulatorSimulate()
        {
            ParameterizedThreadStart threadStart = new ParameterizedThreadStart(this.ThreadSimulateMethod);
            Thread th = new Thread(threadStart);
            // 1st kind - a - synchronus - fast
            // 2nd kind - s - asynchronus - slower
            // 3rd kind - r - randomly - slowest
            th.Start("s");
        }

        private void AttractorAddToGrid()
        {
            this.Invoke(new MethodInvoker(() =>
            {
                this.dataTable.Rows.Add();

                this.PredictorAttractorsReCreate();

                if (this.ChangedAttractorList != null)
                {
                    this.ChangedAttractorList(this, EventArgs.Empty);
                }
            }));
        }

        private void ThreadAddAttractorMethod()
        {
            lock (Mutex)
            {
                this.AttractorAddToGrid();
            }
        }

        public void AttractorAdd()
        {
            ThreadStart threadStart = new ThreadStart(this.ThreadAddAttractorMethod);
            Thread th = new Thread(threadStart);
            th.Start();
        }

        private void AttractorRemoveFromGrid()
        {
            this.Invoke(new MethodInvoker(() =>
            {
                foreach (var selectedRow in this.dataGridView.SelectedRows)
                {
                    this.dataGridView.Rows.Remove(selectedRow as DataGridViewRow);
                }

                this.PredictorAttractorsReCreate();

                if (this.ChangedAttractorList != null)
                {
                    this.ChangedAttractorList(this, EventArgs.Empty);
                }
            }));
        }

        private void ThreadRemoveAttractorMethod()
        {
            lock (Mutex)
            {
                this.AttractorRemoveFromGrid();
            }
        }

        public void AttractorRemoveSelected()
        {
            ThreadStart threadStart = new ThreadStart(this.ThreadRemoveAttractorMethod);
            Thread th = new Thread(threadStart);
            th.Start();
        }

        private void AttractorsChangedInTable()
        {
            this.PredictorAttractorsReCreate();

            if (this.ChangedAttractorList != null)
            {
                this.ChangedAttractorList(this, EventArgs.Empty);
            }
        }

        private void dataTable_TableNewRow(object sender, System.Data.DataTableNewRowEventArgs e)
        {
            this.AttractorsChangedInTable();
        }

        private void dataTable_RowDeleted(object sender, System.Data.DataRowChangeEventArgs e)
        {
            this.AttractorsChangedInTable();
        }

        private void saveToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            this.FileSave();
        }

        private void loadToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.FileOpen();
        }

        private void textBoxD_TextChanged(object sender, EventArgs e)
        {
            int d = 0;
            if (true == int.TryParse(textBoxD.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out d))
            {
                this.Hopfield.Setd(d);

                if (this.ChangedD != null)
                {
                    this.ChangedD(this, EventArgs.Empty);
                }
            }
        }

        private void textBoxEta_TextChanged(object sender, EventArgs e)
        {
            float eta = 0.7f;
            if (true == float.TryParse(textBoxEta.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out eta))
            {
                this.Hopfield.Seteta(eta);

                if (this.ChangedEta != null)
                {
                    this.ChangedEta(this, EventArgs.Empty);
                }
            }
        }

        private void textBoxN1_TextChanged(object sender, EventArgs e)
        {
            int n = 0;
            if (true == int.TryParse(textBoxN1.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out n))
            {
                this.pN1 = n;
                this.PredictorGridReCreate(this.pN1);
            }
        }

        private void textBoxN2_TextChanged(object sender, EventArgs e)
        {
            int n = 0;
            if (true == int.TryParse(textBoxN2.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out n))
            {
                this.pN2 = n;
            }
        }

        private void textBoxDelta_TextChanged(object sender, EventArgs e)
        {
            int delta = 0;
            if (true == int.TryParse(textBoxDelta.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out delta))
            {
                this.pDelta = delta;
            }
        }

        #endregion

        #region Events

        public event EventHandler ChangedAttractorList;
        public event EventHandler ChangedD;
        public event EventHandler ChangedEta;

        #endregion
    }
}
